package com.inspinia.base.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;

/**
 * 该线程为通知下载进度的线程。
 * 用于在下载未完成时通知用户下载的进度，范围（0-100）。0表示文件还未开始下载；100则表示文件下载完成。
 * 此时下载瞬时速度（单位：kb/每秒）。
 * 在完成时返回下载完成的File对象给用户。返回下载所用的总时间（单位为毫秒）给用户。
 *
 * @author wangjie
 * @version 创建时间：2013-2-17 下午12:23:59
 */
public class FileDownloaderNotificationThread extends Thread {


    /**
     * 日志对象
     */
    protected static Logger logger = LoggerFactory.getLogger(FileDownloaderNotificationThread.class);

    private final File tempFile;
    private final File positionFile;
    private FileDownloadThread[] threads;
    private FileDownloadListener fileDownloadListener;
    private File destFile;
    private long destFileSize;
    private boolean isRunning = true; // 线程运行停止标志
    private boolean isNotification; // 通知标志


    /**
     * 通过该方法构建一个进度通知线程。
     *
     * @param threads              下载某文件需要的所有线程。
     * @param fileDownloadListener 要通知进度的监听器对象。
     * @param destFile             下载的文件对象。
     * @param tempFile
     * @param positionFile
     */
    public FileDownloaderNotificationThread(FileDownloadThread[] threads,
                                            FileDownloadListener fileDownloadListener, File destFile, long destFileSize, File tempFile, File positionFile) {
        this.threads = threads;
        this.fileDownloadListener = fileDownloadListener;
        this.destFile = destFile;
        this.destFileSize = destFileSize;
        this.tempFile = tempFile;
        this.positionFile = positionFile;
    }

    long oldTemp = 0; // 上次已下载数据长度
    long oldTime = 0; // 上次下载的当前时间
    long startTime = 0;

    /**
     * 不断地循环来就检查更新进度。
     */
    @Override
    public void run() {

        startTime = System.currentTimeMillis(); // 文件下载开始时间

        while (isRunning) {
            if (logger.isDebugEnabled()) {
                logger.debug("正在计算进度");
            }

            // 计算此时的所有线程下载长度的总和
            long downloadLength = countCurrDownloadLength();

            calcSpeed(downloadLength);

            // 如果下载的文件大小大于或等于需要下载的文件大小，则认为下载完成。使用progress==100并不可靠。
            if (downloadLength >= destFileSize) {
                downloadCompleted();
            }
            // 进行检查并更新通知
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                logger.error("", e);
            }

        }
    }
    //下载完成方式是否被条用过，true表示被调用过。
    private boolean calledDownloadCompletedMethod = false;

    /**
     * 下载完成，该方法只能被执行一次。因为有可能被本线程和FileDownloader调用。
     */
    public synchronized void downloadCompleted() {
        if (!calledDownloadCompletedMethod) {
            //保证方法只能被执行一次。
            calledDownloadCompletedMethod = true;
            try {
                afterDownloadComplete();

                // 通知监听器，并传入新的文件对象
                if (null != fileDownloadListener) {
                    fileDownloadListener.downloadCompleted(destFile, System.currentTimeMillis() - startTime);
                }
            } catch (IOException e) {
                logger.error("", e);
            } finally {
                isRunning = false; // 文件下载完就结束通知线程。
            }
        }
    }

    /**
     * 计算此时的所有线程下载长度的总和
     *
     * @return
     */
    private long countCurrDownloadLength() {
        long temp = 0;
        for (FileDownloadThread thread : threads) {
            if (thread != null) {
                temp += thread.getDownloadedLength();
            }
        }
        return temp;
    }

    /**
     * 计算下载速度
     *
     * @param downloadLength
     * @return
     */
    private void calcSpeed(long downloadLength) {

        // 换算成进度
        int progress = (int) ((double) downloadLength * 100 / (double) destFileSize);

        // 把进度通知给监听器
        if (null != fileDownloadListener) {
            // 计算瞬时速度
            long detaTemp = downloadLength - oldTemp; // 两次更新进度的时间段内的已下载数据差
            long detaTime = System.currentTimeMillis() - oldTime; // 两次更新进度的时间段内的时间差
            // 两次更新进度的时间段内的速度作为瞬时速度
            double speed = ((double) detaTemp / 1024) / ((double) (detaTime) / 1000);

            // 保留小数点后2位，最后一位四舍五入
            speed = new BigDecimal(speed).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();

            // 计算剩余下载时间
            double remainTime = (double) (destFileSize - downloadLength) / speed;
            if (Double.isInfinite(remainTime) || Double.isNaN(remainTime)) {
                remainTime = 0;
            } else {
                remainTime = new BigDecimal(remainTime).setScale(0, BigDecimal.ROUND_HALF_UP).longValue();
            }

            // 通知监听者进度和速度以及下载剩余时间
            fileDownloadListener.downloadProgress(progress, speed, (long) remainTime);

            // 重置上次已下载数据长度和上次下载的当前时间
            oldTemp = downloadLength;
            oldTime = System.currentTimeMillis();
        }
    }

    private void afterDownloadComplete() throws IOException {
        //如果是覆盖方式，则直接将临时文件内容写入到本地文件。
        if (!destFile.exists()) {
            destFile.getParentFile().mkdirs();
            destFile.createNewFile();
        }
        //合并文件
        FileUtil.appendFile(destFile, tempFile);

        //删除位置文件
        Files.delete(positionFile.toPath());
    }


    public boolean isRunning() {
        return isRunning;
    }

    /**
     * 取消该通知线程
     *
     * @author wangjie
     */
    public void cancelThread() {
        isRunning = false;
        isNotification = false;
    }


    //是否推送过错误信息
    private volatile boolean pushedError = false;

    /**
     * 通知线程下载失败。
     *
     * @param e
     */
    public synchronized void notificationError(Exception e) {
        //保证只给监听者推送一次。
        if (!pushedError) {
            pushedError = true;
            cancelThread();
            fileDownloadListener.downloadError(e);
        }
    }
}